package org.mapdb.volume; import org.jetbrains.annotations.Nullable; import org.mapdb.CC; import org.mapdb.DataIO; import org.mapdb.DataInput2; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; /** * In-memory Volume which uses `sun.misc.Unsafe` to access memory directly. * It does not perform boundary checks and is faster then `byte[]` or `DirectByteBuffer`. */ public class UnsafeVolume extends Volume { static final Logger LOG = Logger.getLogger(UnsafeVolume.class.getName()); static final sun.misc.Unsafe UNSAFE = getUnsafe(); @SuppressWarnings("restriction") private static sun.misc.Unsafe getUnsafe() { if(ByteOrder.nativeOrder()!=ByteOrder.LITTLE_ENDIAN){ LOG.log(Level.WARNING,"This is not Little Endian platform. Unsafe optimizations are disabled."); return null; } try { java.lang.reflect.Field singleoneInstanceField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); singleoneInstanceField.setAccessible(true); sun.misc.Unsafe ret = (sun.misc.Unsafe)singleoneInstanceField.get(null); return ret; } catch (Throwable e) { LOG.log(Level.WARNING,"Could not instantiate sun.misc.Unsafe. Fall back to DirectByteBuffer and other alternatives.",e); return null; } } private static final long BYTE_ARRAY_OFFSET; private static final int BYTE_ARRAY_SCALE; private static final long INT_ARRAY_OFFSET; private static final int INT_ARRAY_SCALE; private static final long SHORT_ARRAY_OFFSET; private static final int SHORT_ARRAY_SCALE; private static final long CHAR_ARRAY_OFFSET; private static final int CHAR_ARRAY_SCALE; static { BYTE_ARRAY_OFFSET = UNSAFE==null?-1:UNSAFE.arrayBaseOffset(byte[].class); BYTE_ARRAY_SCALE = UNSAFE==null?-1:UNSAFE.arrayIndexScale(byte[].class); INT_ARRAY_OFFSET = UNSAFE==null?-1:UNSAFE.arrayBaseOffset(int[].class); INT_ARRAY_SCALE = UNSAFE==null?-1:UNSAFE.arrayIndexScale(int[].class); SHORT_ARRAY_OFFSET = UNSAFE==null?-1:UNSAFE.arrayBaseOffset(short[].class); SHORT_ARRAY_SCALE = UNSAFE==null?-1:UNSAFE.arrayIndexScale(short[].class); CHAR_ARRAY_OFFSET = UNSAFE==null?-1:UNSAFE.arrayBaseOffset(char[].class); CHAR_ARRAY_SCALE = UNSAFE==null?-1:UNSAFE.arrayIndexScale(char[].class); } public static boolean unsafeAvailable(){ return UNSAFE !=null; } // Cached array base offset private static final long ARRAY_BASE_OFFSET = UNSAFE ==null?-1 : UNSAFE.arrayBaseOffset(byte[].class);; public static final VolumeFactory FACTORY = new VolumeFactory() { @Override public boolean handlesReadonly() { return false; } @Override //TODO handle all extra params public Volume makeVolume(String file, boolean readOnly, long fileLockWait, int sliceShift, long initSize, boolean fixedSize) { return new UnsafeVolume(0,sliceShift, initSize); } @Override public boolean exists(@Nullable String file) { return false; } }; // This number limits the number of bytes to copy per call to Unsafe's // copyMemory method. A limit is imposed to allow for safepoint polling // during a large copy static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L; static void copyFromArray(byte[] src, long srcPos, long dstAddr, long length) { long offset = ARRAY_BASE_OFFSET + srcPos; while (length > 0) { long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length; UNSAFE.copyMemory(src, offset, null, dstAddr, size); length -= size; offset += size; dstAddr += size; } } static void copyToArray(long srcAddr, byte[] dst, long dstPos, long length) { long offset = ARRAY_BASE_OFFSET + dstPos; while (length > 0) { long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length; UNSAFE.copyMemory(null, srcAddr, dst, offset, size); length -= size; srcAddr += size; offset += size; } } protected volatile long[] addresses= new long[0]; protected volatile sun.nio.ch.DirectBuffer[] buffers = new sun.nio.ch.DirectBuffer[0]; protected final long sizeLimit; protected final boolean hasLimit; protected final int sliceShift; protected final int sliceSizeModMask; protected final int sliceSize; protected final ReentrantLock growLock = new ReentrantLock(CC.FAIR_LOCK); public UnsafeVolume() { this(0, CC.PAGE_SHIFT,0L); } public UnsafeVolume(long sizeLimit, int sliceShift, long initSize) { this.sizeLimit = sizeLimit; this.hasLimit = sizeLimit>0; this.sliceShift = sliceShift; this.sliceSize = 1<< sliceShift; this.sliceSizeModMask = sliceSize -1; if(initSize!=0) ensureAvailable(initSize); } @Override public void ensureAvailable(long offset) { offset= DataIO.roundUp(offset,1L<<sliceShift); //*LOG*/ System.err.printf("tryAvailabl: offset:%d\n",offset); //*LOG*/ System.err.flush(); if(hasLimit && offset>sizeLimit) { //return false; throw new IllegalAccessError("too big"); //TODO size limit here } int slicePos = (int) (offset >>> sliceShift); //check for most common case, this is already mapped if (slicePos < addresses.length){ return; } growLock.lock(); try{ //check second time if(slicePos<= addresses.length) return; //already enough space int oldSize = addresses.length; long[] addresses2 = addresses; sun.nio.ch.DirectBuffer[] buffers2 = buffers; int newSize = slicePos; addresses2 = Arrays.copyOf(addresses2, newSize); buffers2 = Arrays.copyOf(buffers2, newSize); for(int pos=oldSize;pos<addresses2.length;pos++) { //take address from DirectByteBuffer so allocated memory can be released by GC sun.nio.ch.DirectBuffer buf = (sun.nio.ch.DirectBuffer) ByteBuffer.allocateDirect(sliceSize); long address = buf.address(); //TODO is cleanup necessary here? //PERF speedup by copying an array for(long i=0;i<sliceSize;i+=8) { UNSAFE.putLong(address + i, 0L); } buffers2[pos]=buf; addresses2[pos]=address; } addresses = addresses2; buffers = buffers2; }finally{ growLock.unlock(); } } @Override public void truncate(long size) { //TODO support truncate } @Override public void putLong(long offset, long value) { //*LOG*/ System.err.printf("putLong: offset:%d, value:%d\n",offset,value); //*LOG*/ System.err.flush(); value = Long.reverseBytes(value); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; UNSAFE.putLong(address + offset, value); } @Override public void putInt(long offset, int value) { //*LOG*/ System.err.printf("putInt: offset:%d, value:%d\n",offset,value); //*LOG*/ System.err.flush(); value = Integer.reverseBytes(value); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; UNSAFE.putInt(address + offset, value); } @Override public void putByte(long offset, byte value) { //*LOG*/ System.err.printf("putByte: offset:%d, value:%d\n",offset,value); //*LOG*/ System.err.flush(); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; UNSAFE.putByte(address + offset, value); } @Override public void putData(long offset, byte[] src, int srcPos, int srcSize) { // for(int pos=srcPos;pos<srcPos+srcSize;pos++){ // UNSAFE.putByte(address+offset+pos,src[pos]); // } //*LOG*/ System.err.printf("putData: offset:%d, srcLen:%d, srcPos:%d, srcSize:%d\n",offset, src.length, srcPos, srcSize); //*LOG*/ System.err.flush(); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; copyFromArray(src, srcPos, address+offset, srcSize); } @Override public void putData(long offset, ByteBuffer buf) { //*LOG*/ System.err.printf("putData: offset:%d, bufPos:%d, bufLimit:%d:\n",offset,buf.position(), buf.limit()); //*LOG*/ System.err.flush(); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; for(int pos=buf.position();pos<buf.limit();pos++){ UNSAFE.putByte(address + offset + pos, buf.get(pos)); } } @Override public long getLong(long offset) { //*LOG*/ System.err.printf("getLong: offset:%d \n",offset); //*LOG*/ System.err.flush(); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; long l = UNSAFE.getLong(address +offset); return Long.reverseBytes(l); } @Override public int getInt(long offset) { //*LOG*/ System.err.printf("getInt: offset:%d\n",offset); //*LOG*/ System.err.flush(); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; int i = UNSAFE.getInt(address +offset); return Integer.reverseBytes(i); } @Override public byte getByte(long offset) { //*LOG*/ System.err.printf("getByte: offset:%d\n",offset); //*LOG*/ System.err.flush(); final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; return UNSAFE.getByte(address +offset); } @Override public DataInput2 getDataInput(long offset, int size) { final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; return new DataInputUnsafe(address, (int) offset); } @Override public void getData(long offset, byte[] bytes, int bytesPos, int size) { final long address = addresses[((int) (offset >>> sliceShift))]; offset = offset & sliceSizeModMask; copyToArray(address+offset,bytes, bytesPos,size); } // @Override // public DataInput2 getDataInput(long offset, int size) { // //*LOG*/ System.err.printf("getDataInput: offset:%d, size:%d\n",offset,size); // //*LOG*/ System.err.flush(); // byte[] dst = new byte[size]; //// for(int pos=0;pos<size;pos++){ //// dst[pos] = UNSAFE.getByte(address +offset+pos); //// } // // final long address = addresses[((int) (offset >>> sliceShift))]; // offset = offset & sliceSizeModMask; // // copyToArray(address+offset, dst, ARRAY_BASE_OFFSET, // 0, // size); // // return new DataInput2(dst); // } @Override public void putDataOverlap(long offset, byte[] data, int pos, int len) { boolean overlap = (offset>>>sliceShift != (offset+len)>>>sliceShift); if(overlap){ while(len>0){ long addr = addresses[((int) (offset >>> sliceShift))]; long pos2 = offset&sliceSizeModMask; long toPut = Math.min(len,sliceSize - pos2); //System.arraycopy(data, pos, b, pos2, toPut); copyFromArray(data,pos,addr+pos2,toPut); pos+=toPut; len -=toPut; offset+=toPut; } }else{ putData(offset,data,pos,len); } } @Override public DataInput2 getDataInputOverlap(long offset, int size) { boolean overlap = (offset>>>sliceShift != (offset+size)>>>sliceShift); if(overlap){ byte[] bb = new byte[size]; final int origLen = size; while(size>0){ long addr = addresses[((int) (offset >>> sliceShift))]; long pos = offset&sliceSizeModMask; long toPut = Math.min(size,sliceSize - pos); //System.arraycopy(b, pos, bb, origLen - size, toPut); copyToArray(addr+pos,bb,origLen-size,toPut); size -=toPut; offset+=toPut; } return new DataInput2.ByteArray(bb); }else{ //return mapped buffer return getDataInput(offset,size); } } @Override public void close() { if(!closed.compareAndSet(false,true)) return; sun.nio.ch.DirectBuffer[] buf2 = buffers; buffers=null; addresses = null; for(sun.nio.ch.DirectBuffer buf:buf2){ buf.cleaner().clean(); } } @Override public void sync() { } @Override public int sliceSize() { return sliceSize; } @Override public boolean isSliced() { return true; } @Override public long length() { return 1L*addresses.length*sliceSize; } @Override public boolean isReadOnly() { return false; } @Override public File getFile() { return null; } @Override public boolean getFileLocked() { return false; } @Override public void clear(long startOffset, long endOffset) { while(startOffset<endOffset){ putByte(startOffset++, (byte) 0); //PERF use batch copy } } public static final class DataInputUnsafe extends DataInput2{ protected final long baseAdress; protected long pos2; public DataInputUnsafe(long baseAdress, int pos) { this.baseAdress = baseAdress; this.pos2 = baseAdress+pos; } @Override public int getPos() { return (int) (pos2-baseAdress); } @Override public void setPos(int pos) { this.pos2 = baseAdress+pos; } @Override public byte[] internalByteArray() { return null; } @Override public java.nio.ByteBuffer internalByteBuffer() { return null; } @Override public void close() { } @Override public long unpackLong() throws IOException { sun.misc.Unsafe UNSAFE = UnsafeVolume.UNSAFE; long pos = pos2; long ret = 0; byte v; do{ //$DELAY$ v = UNSAFE.getByte(pos++); ret = (ret<<7 ) | (v & 0x7F); }while((v&0x80)==0); pos2 = pos; return ret; } @Override public int unpackInt() throws IOException { sun.misc.Unsafe UNSAFE = UnsafeVolume.UNSAFE; long pos = pos2; int ret = 0; byte v; do{ //$DELAY$ v = UNSAFE.getByte(pos++); ret = (ret<<7 ) | (v & 0x7F); }while((v&0x80)==0); pos2 = pos; return ret; } @Override public long[] unpackLongArrayDeltaCompression(final int size) throws IOException { sun.misc.Unsafe UNSAFE = UnsafeVolume.UNSAFE; long[] ret = new long[size]; long pos2_ = pos2; long prev=0; byte v; for(int i=0;i<size;i++){ long r = 0; do { //$DELAY$ v = UNSAFE.getByte(pos2_++); r = (r << 7) | (v & 0x7F); } while ((v&0x80)==0); prev+=r; ret[i]=prev; } pos2 = pos2_; return ret; } @Override public void unpackLongArray(long[] array, int start, int end) { sun.misc.Unsafe UNSAFE = UnsafeVolume.UNSAFE; long pos2_ = pos2; long ret; byte v; for(;start<end;start++) { ret = 0; do { //$DELAY$ v = UNSAFE.getByte(pos2_++); ret = (ret << 7) | (v & 0x7F); } while ((v&0x80)==0); array[start] = ret; } pos2 = pos2_; } @Override public void unpackIntArray(int[] array, int start, int end) { sun.misc.Unsafe UNSAFE = UnsafeVolume.UNSAFE; long pos2_ = pos2; int ret; byte v; for(;start<end;start++) { ret = 0; do { //$DELAY$ v = UNSAFE.getByte(pos2_++); ret = (ret << 7) | (v & 0x7F); } while ((v&0x80)==0); array[start]=ret; } pos2 = pos2_; } @Override public void unpackLongSkip(int count) throws IOException { sun.misc.Unsafe UNSAFE = UnsafeVolume.UNSAFE; long pos2_ = pos2; while(count>0){ count -= (UNSAFE.getByte(pos2_++)&0x80)>>7; } pos2 = pos2_; } @Override public void readFully(byte[] b) throws IOException { copyToArray(pos2, b, 0, b.length); pos2+=b.length; } @Override public void readFully(byte[] b, int off, int len) throws IOException { copyToArray(pos2,b,off,len); pos2+=len; } @Override public int skipBytes(int n) throws IOException { pos2+=n; return n; } @Override public boolean readBoolean() throws IOException { return readByte()==1; } @Override public byte readByte() throws IOException { return UNSAFE.getByte(pos2++); } @Override public int readUnsignedByte() throws IOException { return UNSAFE.getByte(pos2++) & 0xFF; } @Override public short readShort() throws IOException { //$DELAY$ return (short)((readByte() << 8) | (readByte() & 0xff)); } @Override public int readUnsignedShort() throws IOException { //$DELAY$ return readChar(); } @Override public char readChar() throws IOException { //$DELAY$ return (char)( ((readByte() & 0xff) << 8) | ((readByte() & 0xff))); } @Override public int readInt() throws IOException { int ret = UNSAFE.getInt(pos2); pos2+=4; return Integer.reverseBytes(ret); } @Override public long readLong() throws IOException { long ret = UNSAFE.getLong(pos2); pos2+=8; return Long.reverseBytes(ret); } @Override public float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } @Override public double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); } @Override public String readLine() throws IOException { return readUTF(); } @Override public String readUTF() throws IOException { final int len = unpackInt(); char[] b = new char[len]; for (int i = 0; i < len; i++) //$DELAY$ b[i] = (char) unpackInt(); return new String(b); } } }